﻿using System;
using System.Collections.Generic;
using System.Text;
using System.Runtime.InteropServices;
using VMExplorer.Win32;

namespace VMExplorer
{
	class RemoteReader
	{
		public static bool ReadMemory (SafeProcessHandle hProcess, MemoryAddress baseAddress, IntPtr buffer, uint size)
		{
			if (hProcess.IsInvalid | hProcess.IsClosed) return false;
      if (!baseAddress.IsValid)
      {
        throw new AccessViolationException (string.Format ("RemoteReader cannot read remote data at the following address : {0}", baseAddress));
      }

			uint uiRead = 0;
			return Native.ReadProcessMemory (hProcess, baseAddress, buffer, size, out uiRead) && (uiRead == size);
		}

		public static IntPtr ReadIntPtr (SafeProcessHandle hProcess, MemoryAddress baseAddress)
		{
			IntPtr buffer = Marshal.AllocHGlobal (4);
			ReadMemory (hProcess, baseAddress, buffer, 4);
			IntPtr retVal = Marshal.ReadIntPtr (buffer);
			Marshal.FreeHGlobal (buffer);
			return retVal;
		}

		public static Int32 ReadInt32 (SafeProcessHandle hProcess, MemoryAddress baseAddress)
		{
			IntPtr buffer = Marshal.AllocHGlobal (4);
			ReadMemory (hProcess, baseAddress, buffer, 4);
			Int32 retVal = Marshal.ReadInt32 (buffer);
			Marshal.FreeHGlobal (buffer);
			return retVal;
		}

		public static Int16 ReadInt16 (SafeProcessHandle hProcess, MemoryAddress baseAddress)
		{
			IntPtr buffer = Marshal.AllocHGlobal (2);
			ReadMemory (hProcess, baseAddress, buffer, 2);
			Int16 retVal = Marshal.ReadInt16 (buffer);
			Marshal.FreeHGlobal (buffer);
			return retVal;
		}

		public static string ReadStringUni (SafeProcessHandle hProcess, MemoryAddress baseAddress, int size)
		{
      if (!baseAddress.IsValid) 
        return string.Empty;

			IntPtr buffer = Marshal.AllocHGlobal (size + 2);
			ReadMemory (hProcess, baseAddress, buffer, (uint)size);
      // Write null char at the end of the buffer
			Marshal.WriteInt16 (buffer, size, 0);
			string retVal = Marshal.PtrToStringUni (buffer/*, size*/);
			Marshal.FreeHGlobal (buffer);
			return retVal;
		}


    public static bool RemoteExecute(SafeProcessHandle hProcess, string moduleName, string procName, out uint returnValue)
    {
      returnValue = 0;

      //  typedef HMODULE (WINAPI *GETMODULEHANDLE)(LPCTSTR);
      //  typedef FARPROC (WINAPI *GETPROCADDRESS)(HMODULE,LPCSTR);
      //  typedef int (__stdcall *Proc_i32_v)();
      //
      //  struct INJLIBINFO
      //  {
      //    GETMODULEHANDLE fnGetModuleHandle;
      //    GETPROCADDRESS fnGetProcAddress;
      //    LPCTSTR lpModuleName;
      //    LPCSTR lpProcName;
      //    DWORD dwSuccess;
      //    DWORD dwResult;
      //  }; 
      //
      //  static DWORD WINAPI RemoteExecute(INJLIBINFO* pInfo)
      //  {
      //    if (pInfo == NULL) return -1;
      //
      //    HMODULE hModule = pInfo->fnGetModuleHandle(pInfo->lpModuleName);
      //    if (hModule == NULL)
      //    {
      //      pInfo->dwSuccess = 0;
      //      pInfo->dwResult = 1;
      //      return 0;
      //    }
      //
      //    Proc_i32_v lpFunction = (Proc_i32_v)pInfo->fnGetProcAddress(hModule, pInfo->lpProcName);
      //    if (lpFunction == NULL)
      //    {
      //      pInfo->dwSuccess = 0;
      //      pInfo->dwResult = 2;
      //      return 0;
      //    }
      //
      //    pInfo->dwSuccess = 1;
      //    pInfo->dwResult = lpFunction();
      //  }

      byte[] bytesCode = {

        0x56,                               // push        esi  
        0x8B,0x74,0x24,0x08,                // mov         esi,dword ptr [esp+8] 
        0x85,0xF6,                          // test        esi,esi 
        0x75,0x05,                          // jne         RemoteExecute+0Eh (40100Eh) 
        0x83,0xC8,0xFF,                     // or          eax,0FFFFFFFFh 
        0xEB,0x3A,                          // jmp         RemoteExecute+48h (401048h) 
        0xFF,0x76,0x08,                     // push        dword ptr [esi+8] 
        0xFF,0x16,                          // call        dword ptr [esi] 
        0x85,0xC0,                          // test        eax,eax 
        0x75,0x0C,                          // jne         RemoteExecute+23h (401023h) 
        0x21,0x46,0x10,                     // and         dword ptr [esi+10h],eax 
        0xC7,0x46,0x14,0x01,0x00,0x00,0x00, // mov         dword ptr [esi+14h],1 
        0xEB,0x23,                          // jmp         RemoteExecute+46h (401046h) 
        0xFF,0x76,0x0C,                     // push        dword ptr [esi+0Ch] 
        0x50,                               // push        eax  
        0xFF,0x56,0x04,                     // call        dword ptr [esi+4] 
        0x85,0xC0,                          // test        eax,eax 
        0x75,0x0C,                          // jne         RemoteExecute+3Ah (40103Ah) 
        0x21,0x46,0x10,                     // and         dword ptr [esi+10h],eax 
        0xC7,0x46,0x14,0x02,0x00,0x00,0x00, // mov         dword ptr [esi+14h],2 
        0xEB,0x0C,                          // jmp         RemoteExecute+46h (401046h) 
        0xC7,0x46,0x10,0x01,0x00,0x00,0x00, // mov         dword ptr [esi+10h],1 
        0xFF,0xD0,                          // call        eax  
        0x89,0x46,0x14,                     // mov         dword ptr [esi+14h],eax 
        0x33,0xC0,                          // xor         eax,eax 
        0x5E,                               // pop         esi  
        0xC2,0x04,0x00,                     // ret         4    

      };


      byte[] bytesModuleName = new UnicodeEncoding().GetBytes(moduleName);
      byte[] bytesProcName = new ASCIIEncoding().GetBytes(procName);

      uint sizeCode = (uint)bytesCode.Length;
      uint sizeModuleName = (uint)bytesModuleName.Length;
      uint sizeProcName = (uint)bytesProcName.Length;
      uint sizeInfoStruct = (uint)typeof(Native.RemoteThreadParameters).StructLayoutAttribute.Size;

      uint allocCode = sizeCode.AlignUp(8);
      uint allocModuleName = (sizeModuleName + 1).AlignUp(8);
      uint allocProcName = (sizeProcName + 1).AlignUp(8);
      uint allocInfoStruct = sizeInfoStruct.AlignUp(8);

      uint remoteSize = allocCode + allocModuleName + allocProcName + allocInfoStruct;

      IntPtr remoteBlock = Native.VirtualAllocEx(hProcess, IntPtr.Zero, remoteSize, Native.PAGE_STATE.MEM_COMMIT, Native.MEMORY_PROTECTION.PAGE_EXECUTE_READWRITE);
      if (remoteBlock == IntPtr.Zero)
        return false;

      IntPtr localBuffer = Marshal.AllocHGlobal((int)sizeInfoStruct);

      try
      {
        int iNbWritten = 0;
        IntPtr currentPointer = remoteBlock;
        
        // write byte code
        IntPtr remoteCode = currentPointer;
        currentPointer = currentPointer.Offset(allocCode);
        if (!Native.WriteProcessMemory(hProcess, remoteCode, bytesCode, sizeCode, out iNbWritten) || (iNbWritten != sizeCode))
          return false;

        // write Module name
        IntPtr remoteModuleName = currentPointer;
        currentPointer = currentPointer.Offset(allocModuleName);
        if (!Native.WriteProcessMemory(hProcess, remoteModuleName, bytesModuleName, sizeModuleName, out iNbWritten) || (iNbWritten != sizeModuleName))
          return false;

        // write Method name
        IntPtr remoteProcName = currentPointer;
        currentPointer = currentPointer.Offset(allocProcName);
        if (!Native.WriteProcessMemory(hProcess, remoteProcName, bytesProcName, sizeProcName, out iNbWritten) || (iNbWritten != sizeProcName))
          return false;

        // write Parameters struct
        Native.RemoteThreadParameters info = new Native.RemoteThreadParameters();
        info.fnGetModuleHandle = Native.GetProcAddress(Native.GetModuleHandle("kernel32.dll"), "GetModuleHandleW");
        info.fnGetProcAddress = Native.GetProcAddress(Native.GetModuleHandle("kernel32.dll"), "GetProcAddress");
        info.lpModuleName = remoteModuleName;
        info.lpProcName = remoteProcName;
        info.dwSuccess = 0;
        info.dwResult = 0;

        byte[] bytesInfoStruct = new byte[sizeInfoStruct];

        Marshal.StructureToPtr(info, localBuffer, false);
        Marshal.Copy(localBuffer, bytesInfoStruct, 0, (int)sizeInfoStruct);

        IntPtr remoteInfoStruct = currentPointer;
        currentPointer = currentPointer.Offset(allocInfoStruct);
        if (!Native.WriteProcessMemory(hProcess, remoteInfoStruct, bytesInfoStruct, sizeInfoStruct, out iNbWritten) || (iNbWritten != sizeInfoStruct))
          return false;

        using (SafeProcessHandle hRemoteThread = Native.CreateRemoteThread(hProcess, IntPtr.Zero, 0, remoteBlock, remoteInfoStruct, 0, IntPtr.Zero))
        {
          if (hRemoteThread.IsInvalid)
            return false;

          Native.WaitForSingleObject(hRemoteThread, unchecked((uint)-1));

          uint exitCode;
          Native.GetExitCodeThread(hRemoteThread, out exitCode);
          if (exitCode == uint.MaxValue)
            return false;
        }

        if (!ReadMemory(hProcess, remoteInfoStruct, localBuffer, sizeInfoStruct))
          return false;

        info = (Native.RemoteThreadParameters)Marshal.PtrToStructure(localBuffer, typeof(Native.RemoteThreadParameters));

        //TODO: throw typed exception
        if (info.dwSuccess == 0)
          return false;

        returnValue = info.dwResult;
        return true;
      }
      finally
      {
        Marshal.FreeHGlobal(localBuffer);
        Native.VirtualFreeEx(hProcess, remoteBlock, 0, Native.PAGE_STATE.MEM_RELEASE);
      }
    }
	}
}
